package com.kuaishou.locallife.open.api.common.http.net;

import static com.kuaishou.locallife.open.api.common.Constants.LOCALLIFE_OPEN_SDK_VERSION;
import static com.kuaishou.locallife.open.api.common.Constants.TraceContext.DEFAULT_PRT_TRACE_CONTEXT;
import static com.kuaishou.locallife.open.api.common.Constants.TraceContext.LANEID;
import static com.kuaishou.locallife.open.api.common.Constants.TraceContext.LEFT_QUOTATION;
import static com.kuaishou.locallife.open.api.common.Constants.TraceContext.RIGHT_QUOTATION;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.StringUtils;

import com.kuaishou.locallife.open.api.common.Constants.ContentType;
import com.kuaishou.locallife.open.api.common.HttpRequestMethod;
import com.kuaishou.locallife.open.api.common.dto.KsHttpResponseDTO;
import com.kuaishou.locallife.open.api.common.http.HttpClient;
import com.kuaishou.locallife.open.api.common.utils.KsStringUtils;
import com.kuaishou.locallife.open.api.common.utils.WebUtils;

/**
 * @author gaojiapei <gaojiapei@kuaishou.com>
 * Created on 2023-02-03
 */
public class KsJdkHttpClient implements HttpClient {
    // 忽略ssl check
    private static boolean ignoreSSLCheck = true;

    // 忽略host check
    private static boolean ignoreHostCheck = true;

    // 连接超时时间
    private int connectTimeout;

    // 读取超时时间
    private int readTimeout;

    private String contentType;

    // 是否调用快手prt测试环境
    private boolean isPrtEnv;

    // prt测试环境泳道
    private String laneId;

    @Override
    public KsHttpResponseDTO doGet(String url, Map<String, String> headers, Map<String, String> bizParams, Proxy proxy)
            throws IOException {
        HttpURLConnection conn = null;
        KsHttpResponseDTO httpResponseDTO = new KsHttpResponseDTO();

        String result = "";
        try {
            // 1. 构建请求URL
            URL getURL = WebUtils.buildGetUrl(url, WebUtils.buildQueryParams(bizParams, "UTF-8"));
            // 2. 获取HttpURLConnection实例
            conn = getConnection(getURL, HttpRequestMethod.GET.name(), this.contentType, headers, this.isPrtEnv, this.laneId, proxy);
            conn.setConnectTimeout(this.connectTimeout);
            conn.setReadTimeout(this.readTimeout);
            // 3. 获取响应结果
            result = getResponseAsString(conn);
            httpResponseDTO.setBody(result);
            httpResponseDTO.setHeaders(conn.getHeaderFields());
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        return httpResponseDTO;
    }

    @Override
    public KsHttpResponseDTO doPost(String url, Map<String, String> headers, String reqJson, Proxy proxy)
            throws IOException {
        HttpURLConnection conn = null;
        KsHttpResponseDTO httpResponseDTO = new KsHttpResponseDTO();
        OutputStream out = null;
        byte[] writeBytes;

        String result = "";

        try {
            // 1. 构建请求URL
            URL postURL = new URL(url);
            // 2. 获取HttpURLConnection实例
            conn = getConnection(postURL, HttpRequestMethod.POST.name(), this.contentType, headers, this.isPrtEnv, this.laneId, proxy);
            conn.setConnectTimeout(this.connectTimeout);
            conn.setReadTimeout(this.readTimeout);
            // 3. 获取写入此链接的输出流
            out = conn.getOutputStream();
            // 4. Json -> byte -> conn.outPutStream
            if (!KsStringUtils.isEmpty(reqJson)) {
                writeBytes = reqJson.getBytes();
                out.write(writeBytes);
            }
            result = getResponseAsString(conn);

            httpResponseDTO.setBody(result);
            httpResponseDTO.setHeaders(conn.getHeaderFields());
        } finally {
            if (out != null) {
                out.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }

        return httpResponseDTO;
    }


    private static HttpURLConnection getConnection(URL url, String method, String contentType,
            Map<String, String> headerMap, boolean isPrtEnv, String laneId, Proxy proxy) throws IOException {
        HttpURLConnection conn;

        if (proxy == null) {
            conn = (HttpURLConnection) url.openConnection();
        } else {
            conn = (HttpURLConnection) url.openConnection(proxy);
        }

        // handler https
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection connHttps = (HttpsURLConnection) conn;
            if (ignoreSSLCheck) {
                try {
                    SSLContext ctx = SSLContext.getInstance("TLS");
                    ctx.init(null, new TrustManager[] {new KsJdkHttpClient.TrustAllTrustManager()}, new SecureRandom());
                    connHttps.setSSLSocketFactory(ctx.getSocketFactory());
                    connHttps.setHostnameVerifier(new HostnameVerifier() {
                        public boolean verify(String hostname, SSLSession session) {
                            return true;
                        }
                    });
                } catch (Exception ex) {
                    throw new IOException(ex.toString());
                }
            } else if (ignoreHostCheck) {
                connHttps.setHostnameVerifier(new HostnameVerifier() {
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
            }

            conn = connHttps;
        }

        // 设置是否从HttpURLConnection读取
        conn.setDoInput(true);
        // 设置是否向HttpURLConnection输出
        if (method.equals(HttpRequestMethod.POST.toString())) {
            conn.setDoOutput(true);
        }
        // 设置请求方式
        conn.setRequestMethod(method);
        // 设置请求属性
        conn.setRequestProperty("Host", url.getHost());
        conn.setRequestProperty("Accept", "text/json,text/xml,text/javascript,application/json");
        conn.setRequestProperty("Content-Type", contentType);
        // 是否调用快手prt预发测试环境
        if (isPrtEnv) {
            conn.setRequestProperty("User-Agent", String.format("prt-ks-locallife-sdk-java.%s", LOCALLIFE_OPEN_SDK_VERSION));
            String lane = KsStringUtils.isBlank(laneId) ? DEFAULT_PRT_TRACE_CONTEXT : LANEID + LEFT_QUOTATION + laneId + RIGHT_QUOTATION;
            headerMap.put("trace-context", lane);
        } else {
            conn.setRequestProperty("User-Agent", String.format("ks-locallife-sdk-java.%s", LOCALLIFE_OPEN_SDK_VERSION));
        }

        if (headerMap != null) {
            for (Object o : headerMap.entrySet()) {
                Map.Entry entry = (Map.Entry) o;
                if (!"KS_HTTP_DNS_HOST".equals(entry.getKey())) {
                    conn.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
                }
            }
        }

        return conn;
    }

    public static class TrustAllTrustManager implements X509TrustManager {
        public TrustAllTrustManager() {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }

    protected static String getResponseAsString(HttpURLConnection conn) throws IOException {
        String charset = getResponseCharset(conn.getContentType());
        if (conn.getResponseCode() < 400) {
            String contentEncoding = conn.getContentEncoding();
            return "gzip".equalsIgnoreCase(contentEncoding)
                   ? getStreamAsString(new GZIPInputStream(conn.getInputStream()), charset)
                   : getStreamAsString(conn.getInputStream(), charset);
        } else {
            if (conn.getResponseCode() == 400) {
                InputStream error = conn.getErrorStream();
                if (error != null) {
                    return getStreamAsString(error, charset);
                }
            }

            throw new IOException(conn.getResponseCode() + " " + conn.getResponseMessage());
        }
    }

    public static String getResponseCharset(String contentType) {
        String charset = "UTF-8";

        if (KsStringUtils.isEmpty(contentType)) {
            return charset;
        }

        String[] params = contentType.split(";");

        for (String paramItem : params) {
            String param = paramItem;
            param = param.trim();
            if (param.startsWith("charset")) {
                String[] pair = param.split("=", 2);
                if (pair.length == 2 && !KsStringUtils.isEmpty(pair[1])) {
                    charset = pair[1].trim();
                }
                break;
            }
        }

        return charset;
    }

    public static String getStreamAsString(InputStream stream, String charset) throws IOException {
        try {
            Reader reader = new InputStreamReader(stream, charset);
            StringBuilder response = new StringBuilder();
            char[] buff = new char[1024];

            int read;
            while ((read = reader.read(buff)) > 0) {
                response.append(buff, 0, read);
            }

            return response.toString();
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    public static class Builder {
        private String contentType;

        // 连接超时时间
        private int connectTimeout;

        // 读取超时时间
        private int readTimeout;

        // 是否调用prt测试环境
        private boolean isPrtEnv;

        // prt测试环境泳道
        private String laneId;

        public Builder() {
            this.contentType = ContentType.JSON;
        }

        public static Builder newBuilder() {
            return new Builder();
        }

        public Builder setContentType(String contentType) {
            this.contentType = contentType;
            return this;
        }

        public Builder setConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        public Builder setReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public Builder setIsPrtEnv(boolean isPrtEnv) {
            this.isPrtEnv = isPrtEnv;
            return this;
        }

        public Builder setLaneId(String laneId) {
            this.laneId = laneId;
            return this;
        }

        public KsJdkHttpClient build() {
            KsJdkHttpClient client = new KsJdkHttpClient();
            client.contentType = this.contentType;
            client.connectTimeout = connectTimeout;
            client.readTimeout = readTimeout;
            client.isPrtEnv = isPrtEnv;
            client.laneId = laneId;

            return client;
        }
    }
}
